package axis.dynamic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.xml.namespace.QName;

import org.apache.axis.AxisEngine;
import org.apache.axis.ConfigurationException;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.Handler;
import org.apache.axis.SimpleTargetedChain;
import org.apache.axis.configuration.SimpleProvider;
import org.apache.axis.deployment.wsdd.WSDDDeployment;
import org.apache.axis.description.ServiceDesc;
import org.apache.axis.handlers.http.URLMapper;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.transport.http.AxisServlet;


/**
 * Extends {@link SoapConfiguration}, adds ability to deploy / undeploy services and a few property setters. 
 */
public class SoapServerConfiguration extends SoapConfiguration
{
  
  /** the deployed services. */
  private final ConcurrentMap<QName, SOAPService> _services = new ConcurrentHashMap<QName, SOAPService>();

  /**
   * Creates a server configuration with a http transport {@link Handler} with a {@link URLMapper}.
   */
  public SoapServerConfiguration() 
  {
    // we need to register a global transport with an URLMapper request handler. 
    // otherwise, Axis won't find our service methods.
    deployTransport("http", new SimpleTargetedChain(new URLMapper(), null, null));
  }

  /**
   * @param disable
   */
  public void setDisableServicesList( boolean disable )
  {
    // synchronization shouldn't be necessary, it's a Hashtable and only modified on startup
    _globalOptions.put(AxisServlet.INIT_PROPERTY_DISABLE_SERVICES_LIST, Boolean.toString(disable));
  }
  
  /**
   * necessary to tell axis which path to put into the links on generated html pages.
   * @param path 
   */
  public void setServicesPath( String path )
  {
    String fixed = path;
    // Damn - currently, Axis absolutely needs these slashes...
    if (! fixed.startsWith("/")) fixed = "/"+fixed;
    if (! fixed.endsWith("/")) fixed = fixed+"/";
    // synchronization shouldn't be necessary, it's a Hashtable and only modified on startup
    _globalOptions.put(AxisServlet.INIT_PROPERTY_SERVICES_PATH, fixed);
  }
  
  /**
   * called from {@link AxisEngine#init()}.
   * @param engine
   * @throws ConfigurationException
   * @see EngineConfiguration#configureEngine(AxisEngine)
   * @see AxisEngine#init()
   */
  @Override
  public void configureEngine( AxisEngine engine ) throws ConfigurationException
  {
    // this also sets the _engine field
    super.configureEngine(engine);
    
    // note: this is reasonably thread safe - if a service is deployed after we call 
    // _services.values(), we check the _engine field and call setEngine() for the new
    // service. it's possible that setEngine() is called twice, but that should be a no-op.
    for (SOAPService service : _services.values()) 
    {
      // this also delegates the type mappings of the services to our global type mapping registry.
      service.setEngine(_engine);
    }
  }

  /**
   * @param name
   * @param service
   */
  public void deployService( String name, SOAPService service )
  {
    SOAPService present = _services.putIfAbsent(new QName(name), service);
    
    if (present != null) throw new IllegalArgumentException("duplicate service name: ["+name+"]");
    
    if (_engine != null)
    {
      service.setEngine(_engine);
    }
    
    
  }

  /**
   * @param name
   */
  public void undeployService( String name )
  {
    _services.remove(new QName(name));
    
  }

  /**
   * @param qname
   * @return service for given name
   * @see EngineConfiguration#getService(QName)
   */
  public SOAPService getService( QName qname ) 
  {
    return _services.get(qname);
  }

  /**
   * This implementation currently just returns null. I don't know how namespace-based dispatching
   * is supposed to work. It should be fairly easy to implement this method. See {@link WSDDDeployment}.
   * The implementation in {@link SimpleProvider} seems to be broken (muddles up namspace and local name).  
   * @param namespace 
   * @return null 
   */
  public SOAPService getServiceByNamespaceURI( String namespace )
  {
    return null;
  }

  /**
   * returns an enumeration of the services currently deployed to this engine
   * @return an enumeration of the services currently deployed to this engine
   */
  public Iterator<ServiceDesc> getDeployedServices()
  {
    List<ServiceDesc> serviceDescs = new ArrayList<ServiceDesc>();
    for (SOAPService service : _services.values())
    {
      serviceDescs.add(service.getServiceDescription());
    }
    
    // sort by service name
    Collections.sort(serviceDescs, SERVICE_DESC_COMPARATOR);
    
    return serviceDescs.iterator();
  }
  
  /** compares service descriptions by their name, ignoring case */
  private static final Comparator<ServiceDesc> SERVICE_DESC_COMPARATOR =
  new Comparator<ServiceDesc>()
  {
    public int compare( ServiceDesc desc1, ServiceDesc desc2 )
    {
      return desc1.getName().compareToIgnoreCase(desc2.getName());
    }
  };

}
